<?php


namespace LazadaApi\Services;


use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;

class BaseLazadaService
{
    const METHOD_GET    = 'GET';
    const METHOD_POST   = 'POST';
    const METHOD_PUT    = 'PUT';
    const METHOD_DELETE = 'DELETE';

    private $_httpClient;
    private $secretKey = '';
    private $appKey = '';
    private $signMethod = 'sha256';
    private $maxRetryTimes = 0;
    private $retryInterval = 1;

    private $country = '';
    private $accessToken = '';
    private $sdkVersion;

    private $commonParams = [];
    private $apiParams = [];
    private $headerParams = [];

    const API_GATEWAY_URL_SG    = "https://api.lazada.sg/rest";
    const API_GATEWAY_URL_MY    = "https://api.lazada.com.my/rest";
    const API_GATEWAY_URL_VN    = "https://api.lazada.vn/rest";
    const API_GATEWAY_URL_TH    = "https://api.lazada.co.th/rest";
    const API_GATEWAY_URL_PH    = "https://api.lazada.com.ph/rest";
    const API_GATEWAY_URL_ID    = "https://api.lazada.co.id/rest";
    const API_AUTHORIZATION_URL = "https://auth.lazada.com/rest";

    const COUNTRY_GATEWAY_MAP
        = [
            'my' => self::API_GATEWAY_URL_MY,
            'sg' => self::API_GATEWAY_URL_SG,
            'id' => self::API_GATEWAY_URL_ID,
            'ph' => self::API_GATEWAY_URL_PH,
            'th' => self::API_GATEWAY_URL_TH,
            'vn' => self::API_GATEWAY_URL_VN,
        ];

    public function __construct()
    {
        $this->_httpClient = new Client(config('lazada.httpConfig', []));
        $this->secretKey   = config('lazada.appSecret');
        $this->appKey      = config('lazada.appKey');
        $this->sdkVersion  = config('lazada.sdkVersion');

        $this->setMaxRetryTimes(config('lazada.maxRetryTimes'));
        $this->setRetryInterval(config('lazada.retryInterval'));
    }

    /**
     * @return string
     */
    public function getCountry(): string
    {
        return $this->country;
    }

    public function setCountry(string $country)
    {
        $this->country = strtolower(trim($country));

        return $this;
    }

    /**
     * @return string
     */
    public function getAccessToken(): string
    {
        return $this->accessToken;
    }

    public function setAccessToken(string $accessToken)
    {
        $this->accessToken = $accessToken;

        return $this;
    }

    protected function generateSign($apiName, $apiParams)
    {
        ksort($apiParams);

        $stringToBeSigned = $apiName;
        foreach ($apiParams as $k => $v) {
            $stringToBeSigned .= "$k$v";
        }
        unset($k, $v);

        return strtoupper($this->hmac_sha256($stringToBeSigned, $this->secretKey));
    }

    protected function getApiGateWay(): string
    {
        $gatewayApi = self::COUNTRY_GATEWAY_MAP[$this->getCountry()] ?? '';
        if (empty($gatewayApi)) {
            throw new \Exception('找不到对应国家API 网关地址:' . $this->getCountry(), 500);
        }

        return $gatewayApi;
    }

    public function addApiParam(string $key, $value)
    {
        if (is_object($value) || is_array($value)) {
            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
        }
        $this->apiParams[$key] = $value;
    }

    public function addHeaderParam(string $key, string $value)
    {
        $this->headerParams[$key] = $value;
    }

    public function request(string $apiName, $method = self::METHOD_POST, array $options = [])
    {
        $this->commonParams['app_key']     = $this->appKey;
        $this->commonParams['sign_method'] = $this->signMethod;
        $this->commonParams['timestamp']   = $this->msecsTime();

        if (!empty($this->getAccessToken())) {
            $this->commonParams['access_token'] = $this->getAccessToken();
        }
        $this->commonParams["partner_id"] = $this->sdkVersion;

        $this->commonParams["sign"] = $this->generateSign($apiName, array_merge($this->apiParams, $this->commonParams));
        try {
            $retryTimes = 0;
            while (true) {
                //FixMe:
                try {
                    $uri = $this->getApiGateWay() . $apiName;
                    if ($method === self::METHOD_GET) {
                        $options['query'] = array_merge($this->commonParams, $this->apiParams);
                        $this->apiParams  = [];
                    } else {
                        $options['query'] = $this->commonParams;
                    }
                    if (!empty($this->apiParams)) {
                        $options['form_params'] = $this->apiParams;
                    }
                    if (!empty($this->headerParams)) {
                        $options['headers'] = $this->headerParams;
                    }
                    Log::debug("Lazada Request: ${uri} [$method]:", ['options' => $options]);
                    $resp     = $this->_httpClient->request($method, $uri, $options);
                    $respJson = json_decode($resp->getBody(), true);
                    Log::debug("Lazada Response: ${uri} [$method]:", ['response' => $respJson, 'headers' => $resp->getHeaders()]);

                    return $respJson;
                } catch (\Exception $e) {
                    if ($retryTimes < $this->maxRetryTimes) {
                        $retryTimes += 1;
                        sleep($this->retryInterval);
                    } else {
                        throw $e;
                    }
                }
            }
        } catch (\Exception $ex) {
            Log::error('LazadaApi 服务异常', ['msg' => $ex->getMessage(), 'code' => $ex->getCode(), 'args' => func_get_args(), 'trace' => $ex->getTraceAsString()]);
            throw new \Exception('Lazada API 服务异常: ' . $ex->getMessage(), 500, $ex);
        }
    }

    public function get($apiName, array $options = [])
    {
        return self::request($apiName, self::METHOD_GET, $options);
    }

    public function post($apiName, array $options = [])
    {
        return self::request($apiName, self::METHOD_POST, $options);
    }

    private function msecsTime()
    {
        return intval(time() * 1000);
    }

    private function hmac_sha256($data, $key)
    {
        return hash_hmac('sha256', $data, $key);
    }

    public function setApiParams(array $apiParams)
    {
        foreach ($apiParams as $paramKey => $apiParam) {
            $this->addApiParam($paramKey, $apiParam);
        }
        unset($paramKey, $apiParam);

        return $this;
    }

    public function setHeaderParams(array $headerParams)
    {
        foreach ($headerParams as $key => $headerParam) {
            $this->addHeaderParam($key, $headerParam);
        }
        unset($key, $headerParam);

        return $this;
    }

    public function generateXMLPayload(array $payload)
    {
        $xml = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Request/>');
        $this->_recursiveArrayToXml($payload, $xml);

        //dealing with <0/>..<n/> issues
        return mb_ereg_replace('<(\/\d+|\d+)>', '', $xml->asXML());
    }

    private function _recursiveArrayToXml(array $data, \SimpleXMLElement &$xmlData)
    {
        foreach ($data as $key => $value) {
            if (is_numeric($key)) {
                $key = 0; //dealing with <0/>..<n/> issues
            }
            if (is_array($value)) {
                $subnode = $xmlData->addChild($key);
                $this->_recursiveArrayToXml($value, $subnode);
            } else {
                $xmlData->addChild("$key", htmlspecialchars("$value"));
            }
        }
    }

    public function setMaxRetryTimes(int $maxRetryTimes): void
    {
        if ($maxRetryTimes < 0) {
            $maxRetryTimes = 0;
        } elseif ($maxRetryTimes > 10) {
            $maxRetryTimes = 10;
        }

        $this->maxRetryTimes = $maxRetryTimes;
    }

    public function setRetryInterval(int $retryInterval): void
    {
        if ($retryInterval < 0) {
            $retryInterval = 0;
        }
        $this->retryInterval = $retryInterval;
    }

    public function getAppKey()
    {
        return $this->appKey;
    }
}